package vo;

import exception.FindException;
import exception.SetException;
import util.INumberRange;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * @author 螺旋仓颉
 * 数独类
 */
public class Sudoku implements INumberRange {
    /**
     * 静态属性，用于存放求解结果.key为创建者的内存地址
     */
    private static final HashMap<Sudoku, ArrayList<Sudoku>> RESULT_MAP = new HashMap<>();
    /**
     * 所属数独类
     */
    private final Sudoku parentSudoku;
    /**
     * 所有格子的集合
     */
    private final ArrayList<Grid> grids = new ArrayList<>();

    /**
     * 将格子按照行归类
     */
    private final HashMap<Integer, ArrayList<Grid>> rowMap = new HashMap<Integer, ArrayList<Grid>>() {{
        for (int item : ROW_RANGE) {
            put(item, new ArrayList<>());
        }
    }};

    /**
     * 将格子按照列归类
     */
    private final HashMap<Integer, ArrayList<Grid>> columnMap = new HashMap<Integer, ArrayList<Grid>>() {{
        for (int item : COLUMN_RANGE) {
            put(item, new ArrayList<>());
        }
    }};

    /**
     * 将格子按照区域归类
     */
    private final HashMap<String, ArrayList<Grid>> rangeMap = new HashMap<String, ArrayList<Grid>>() {{
        for (String rowRange : ROW_RANGE_MAP.values()) {
            for (String columnRange : COLUMN_RANGE_MAP.values()) {
                put(rowRange + columnRange, new ArrayList<>());
            }
        }
    }};

    /**
     * @param str      输入的初始数据
     * @param splitStr 分隔符
     * @throws SetException 抛出输入异常
     */
    public Sudoku(String str, String splitStr) throws SetException {
        //过滤输入
        String[] pureStrArr = str.replace("\n", splitStr).split(splitStr);
        //判断输入是否合法
        int length = pureStrArr.length;
        if (length != ROW_RANGE.size() * COLUMN_RANGE.size()) {
            throw new SetException("输入不符合要求，创建失败");
        }
        //创建格子并分类
        for (int index = 0; index < length; index++) {
            Grid grid = new Grid();
            int row = index / COLUMN_RANGE.size() + 1;
            grid.setRow(row);
            int column = (index + 1) % ROW_RANGE.size();
            column = (column == 0) ? ROW_RANGE.size() : column;
            grid.setColumn(column);
            grid.setPresentNum(pureStrArr[index]);
            //初始化格子的可能性list
            if (grid.isEnsure()) {
                grid.getPossibleNum().add(grid.getPresentNum());
            } else {
                grid.setPossibleNum(new ArrayList<>(NUMBER_RANGE));
            }
            this.rowMap.get(row).add(grid);
            this.columnMap.get(column).add(grid);
            this.rangeMap.get(grid.getRange()).add(grid);
            this.grids.add(grid);
        }
        this.parentSudoku = this;
        RESULT_MAP.put(this, new ArrayList<>());
        this.solve();
    }

    private Sudoku(Sudoku parentSudoku, String str, String splitStr) throws SetException {
        //过滤输入
        String[] pureStrArr = str.replace("\n", splitStr).split(splitStr);
        //判断输入是否合法
        int length = pureStrArr.length;
        if (length != ROW_RANGE.size() * COLUMN_RANGE.size()) {
            throw new SetException("输入不符合要求，创建失败");
        }
        //创建格子并分类
        for (int index = 0; index < length; index++) {
            Grid grid = new Grid();
            int row = index / COLUMN_RANGE.size() + 1;
            grid.setRow(row);
            int column = (index + 1) % ROW_RANGE.size();
            column = (column == 0) ? ROW_RANGE.size() : column;
            grid.setColumn(column);
            grid.setPresentNum(pureStrArr[index]);
            //初始化格子的可能性list
            if (grid.isEnsure()) {
                grid.getPossibleNum().add(grid.getPresentNum());
            } else {
                grid.setPossibleNum(new ArrayList<>(NUMBER_RANGE));
            }
            this.rowMap.get(row).add(grid);
            this.columnMap.get(column).add(grid);
            this.rangeMap.get(grid.getRange()).add(grid);
            this.grids.add(grid);
        }
        this.parentSudoku = parentSudoku;
        this.solve();
    }

    /**
     * 刷新每个格子，并返回刷新结果
     */
    private RefreshResult refresh() {
        //新建结果类
        RefreshResult refreshResult = new RefreshResult();
        for (Grid grid : this.grids) {
            if (!grid.isEnsure()) {
                //去除同行中的元素
                for (Grid rowGrid : this.rowMap.get(grid.getRow())) {
                    if (rowGrid.isEnsure()) {
                        grid.getPossibleNum().remove(rowGrid.getPresentNum());
                    }
                }
                //去除同列中的元素
                for (Grid columnGrid : this.columnMap.get(grid.getColumn())) {
                    if (columnGrid.isEnsure()) {
                        grid.getPossibleNum().remove(columnGrid.getPresentNum());
                    }
                }
                //去除同区域中的元素
                for (Grid rangeGrid : this.rangeMap.get(grid.getRange())) {
                    if (rangeGrid.isEnsure()) {
                        grid.getPossibleNum().remove(rangeGrid.getPresentNum());
                    }
                }
                //刷新格子,尝试解决,并判断是否无解,无解则退出循环
                if (!grid.refresh()) {
                    refreshResult.flag = false;
                    break;
                }
                //判断刷新后是否有解，并统计未知个数
                if (!grid.isEnsure()) {
                    refreshResult.unknownGrids.add(grid);
                }
            }
        }
        return refreshResult;
    }

    /**
     * 用于表示刷新结果的内部类
     */
    private static class RefreshResult {
        /**
         * 目前输入的数独不是无解
         */
        boolean flag = true;
        /**
         * 目前的数独未知的格子
         */
        ArrayList<Grid> unknownGrids = new ArrayList<>();
    }

    /**
     * 对数独进行求解
     */
    public void solve() throws StackOverflowError, OutOfMemoryError, SetException {
        RefreshResult refreshResult = new RefreshResult();
        //上一轮刷新后的未知格子个数
        int oldNum;
        //一直求解直到无解、需要猜测或解出结果
        while (true) {
            oldNum = refreshResult.unknownGrids.size();
            refreshResult = this.refresh();
            if (refreshResult.flag) {
                if (refreshResult.unknownGrids.size() == 0) {
                    //有解退出循环
                    RESULT_MAP.get(this.parentSudoku).add(this);
                    break;
                } else if (oldNum == refreshResult.unknownGrids.size()) {
                    //需要猜测则进行猜测
                    Grid grid = refreshResult.unknownGrids.get(0);
                    for (String guessStr : grid.getPossibleNum()) {
                        //新建一张数独实体类，将猜测后的数据带入并求解
                        grid.setPresentNum(guessStr);
                        String splitStr = "、";
                        Sudoku sudoku = new Sudoku(this.parentSudoku, this.toString(splitStr), splitStr);
                        grid.setPresentNum("0");
                    }
                    break;
                }
            } else {
                break;
            }
        }
    }

    /**
     * 打印结果
     */
    public void print() throws FindException {
        if (RESULT_MAP.containsKey(this)) {
            ArrayList<Sudoku> sudokuResult = Sudoku.RESULT_MAP.get(this);
            if (sudokuResult.size() == 0) {
                System.out.println("数独无解");
            } else {
                for (Sudoku sudoku : sudokuResult) {
                    System.out.println("---------------------------------------");
                    for (ArrayList<Grid> grids : sudoku.rowMap.values()) {
                        for (Grid grid : grids) {
                            System.out.print(grid.getPresentNum());
                            System.out.print("\t");
                        }
                        System.out.println();
                    }
                }
            }
        } else {
            throw new FindException("打印失败，未找到目标");
        }
    }


    /**
     * 将数独表输出为一个字符串
     *
     * @param splitStr 输出字符串的分隔符
     */
    private String toString(String splitStr) {
        StringBuilder str = new StringBuilder();
        for (ArrayList<Grid> grids : this.rowMap.values()) {
            for (Grid grid : grids) {
                str.append(grid.getPresentNum()).append(splitStr);
            }
        }
        return str.toString();
    }

    /**
     * 删除map中的解，释放内存
     */
    public void delSudoku() throws FindException {
        if (RESULT_MAP.containsKey(this)) {
            Sudoku.RESULT_MAP.remove(this);
        } else {
            throw new FindException("删除失败，未找到目标");
        }
    }

    /**
     * 删除map中的解，释放内存
     */
    public static void delSudoku(Sudoku sudoku) throws FindException {
        if (RESULT_MAP.containsKey(sudoku)) {
            Sudoku.RESULT_MAP.remove(sudoku);
        } else {
            throw new FindException("删除失败，未找到目标");
        }
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    /**
     * 返回求解结果
     *
     * @return 字符串列表
     * @throws FindException 未找到目标的异常
     */
    public ArrayList<String> getResults() throws FindException {
        ArrayList<String> results = new ArrayList<>();
        if (RESULT_MAP.containsKey(this)) {
            ArrayList<Sudoku> sudokuResult = Sudoku.RESULT_MAP.get(this);
            for (Sudoku sudoku : sudokuResult) {
                StringBuilder result = new StringBuilder();
                for (ArrayList<Grid> grids : sudoku.rowMap.values()) {
                    for (Grid grid : grids) {
                        result.append(grid.getPresentNum()).append("\t");
                    }
                    result.append("\n");
                }
                results.add(result.toString());
            }
        } else {
            throw new FindException("输出失败，未找到目标");
        }
        return results;
    }
}
